﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using SocialApi.Core.Extensions;
using SocialApi.Core.Patterns;

namespace SocialApi.Core.Request
{
    /// <summary>
    /// Выполняет синхронные/асинхронные запросы на сервер
    /// </summary>
    public class Navigator : Singleton<Navigator>, IRequest
    {
        #region Настройки

        public static string GetData(object param)
        {
            var properties = param.GetType().GetProperties();
            var first = properties.FirstOrDefault();
#if NET_4_5
            var result = new StringBuilder(string.Format("{0}={1}", first.Name, first.GetValue(param)));
            foreach (var propertyInfo in properties.Skip(1))
            {
                result.AppendFormat("&{0}={1}", propertyInfo.Name, propertyInfo.GetValue(param));
            }
#else
            var result = new StringBuilder(string.Format("{0}={1}", first.Name, first.GetValue(param, null)));
            foreach (var propertyInfo in properties.Skip(1))
            {
                result.AppendFormat("&{0}={1}", propertyInfo.Name, propertyInfo.GetValue(param, null));
            }
#endif
            return result.ToString();
        }

        private string CreateRequest(string url, string data, HttpMethod httpMethod)
        {
            var webRequest = WebRequest.Create(url) as HttpWebRequest;
            try
            {
                webRequest.ContentType = "application/x-www-form-urlencoded";
                webRequest.UserAgent = UserAgent;
                switch (httpMethod)
                {
                    case HttpMethod.Get:
                    case HttpMethod.Head:
                        webRequest.Method = httpMethod.ToString().ToUpperInvariant();
                        using (var webResponse = webRequest.GetResponse() as HttpWebResponse)
                        {
                            using (var streamReader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8))
                            {
                                return streamReader.ReadToEnd();
                            }
                        }
                    case HttpMethod.Post:
                    case HttpMethod.Put:
                    case HttpMethod.Delete:
                        webRequest.Method = httpMethod.ToString().ToUpperInvariant();
                        if (data.Exists())
                        {
                            using (var streamWriter = webRequest.GetRequestStream())
                            {
                                var bytes = Encoding.UTF8.GetBytes(data);
                                streamWriter.Write(bytes, 0, bytes.Length);
                            }
                        }
                        using (var webResponse = webRequest.GetResponse() as HttpWebResponse)
                        {
                            using (var streamReader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8))
                            {
                                return streamReader.ReadToEnd();
                            }
                        }
                }
            }
            catch (WebException exception)
            {
                if (Falied != null)
                {
#if NET_4_5
                    Falied(this, exception);
#else
                    Falied(this, new EventArgs<WebException>(exception));
#endif
                    return null;
                }
                throw new WebException(exception.Message);
            }
            return null;
        }

        private Navigator() { }

        #endregion

        #region Члены IRequest

        /// <summary>
        /// Агент пользователя
        /// </summary>
        public string UserAgent { get { return GetType().Assembly.FullName; } }

        /// <summary>
        /// Возникает при неудачной отправке запроса
        /// </summary>
#if NET_4_5
        public event EventHandler<WebException> Falied;
#else
        public event EventHandler<EventArgs<WebException>> Falied;
#endif

        #endregion

        #region Члены IGet

        /// <summary>
        /// Выполняет синхронный GET запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Get(Uri url)
        {
            return CreateRequest(url.AbsoluteUri, null, HttpMethod.Get);
        }

        /// <summary>
        /// Выполняет синхронный GET запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Get(string url)
        {
            return CreateRequest(url, null, HttpMethod.Get);
        }

        #endregion

        #region Члены IGetAsync

        /// <summary>
        /// Выполняет асинхронный GET запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> GetAsync(Uri url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, null, HttpMethod.Get));
        }

        /// <summary>
        /// Выполняет асинхронный GET запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> GetAsync(string url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, null, HttpMethod.Get));
        }

        #endregion

        #region Члены IPost

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Post(Uri url, object data)
        {
            return CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Post);
        }

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Post(Uri url, string data)
        {
            return CreateRequest(url.AbsoluteUri, data, HttpMethod.Post);
        }

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Post(Uri url)
        {
            return CreateRequest(url.AbsoluteUri, null, HttpMethod.Post);
        }

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Post(string url, object data)
        {
            return CreateRequest(url, GetData(data), HttpMethod.Post);
        }

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Post(string url, string data)
        {
            return CreateRequest(url, data, HttpMethod.Post);
        }

        /// <summary>
        /// Выполняет синхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Post(string url)
        {
            return CreateRequest(url, null, HttpMethod.Post);
        }

        #endregion

        #region Члены IPostAsync

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PostAsync(Uri url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Post));
        }

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PostAsync(Uri url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, data, HttpMethod.Post));
        }

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> PostAsync(Uri url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, null, HttpMethod.Post));
        }

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PostAsync(string url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, GetData(data), HttpMethod.Post));
        }

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PostAsync(string url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, data, HttpMethod.Post));
        }

        /// <summary>
        /// Выполняет асинхронный POST запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> PostAsync(string url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, null, HttpMethod.Post));
        }

        #endregion

        #region Члены IPut

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Put(Uri url, object data)
        {
            return CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Put);
        }

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Put(Uri url, string data)
        {
            return CreateRequest(url.AbsoluteUri, data, HttpMethod.Put);
        }

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Put(Uri url)
        {
            return CreateRequest(url.AbsoluteUri, null, HttpMethod.Put);
        }

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Put(string url, object data)
        {
            return CreateRequest(url, GetData(data), HttpMethod.Put);
        }

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Put(string url, string data)
        {
            return CreateRequest(url, data, HttpMethod.Put);
        }

        /// <summary>
        /// Выполняет синхронный PUT запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Put(string url)
        {
            return CreateRequest(url, null, HttpMethod.Put);
        }

        #endregion

        #region Члены IPutAsync

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PutAsync(Uri url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Put));
        }

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PutAsync(Uri url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, data, HttpMethod.Put));
        }

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> PutAsync(Uri url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, null, HttpMethod.Put));
        }

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PutAsync(string url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, GetData(data), HttpMethod.Put));
        }

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> PutAsync(string url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, data, HttpMethod.Put));
        }

        /// <summary>
        /// Выполняет асинхронный Put запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> PutAsync(string url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, null, HttpMethod.Put));
        }

        #endregion

        #region Члены IDelete

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Delete(Uri url, object data)
        {
            return CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Delete);
        }

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Delete(Uri url, string data)
        {
            return CreateRequest(url.AbsoluteUri, data, HttpMethod.Delete);
        }

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Delete(Uri url)
        {
            return CreateRequest(url.AbsoluteUri, null, HttpMethod.Delete);
        }

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Delete(string url, object data)
        {
            return CreateRequest(url, GetData(data), HttpMethod.Delete);
        }

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public string Delete(string url, string data)
        {
            return CreateRequest(url, data, HttpMethod.Delete);
        }

        /// <summary>
        /// Выполняет синхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Delete(string url)
        {
            return CreateRequest(url, null, HttpMethod.Delete);
        }

        #endregion

        #region Члены IDeleteAsync

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> DeleteAsync(Uri url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, GetData(data), HttpMethod.Delete));
        }

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> DeleteAsync(Uri url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, data, HttpMethod.Delete));
        }

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> DeleteAsync(Uri url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, null, HttpMethod.Delete));
        }

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> DeleteAsync(string url, object data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, GetData(data), HttpMethod.Delete));
        }

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        /// <param name="data">Параметры запроса</param>
        public Task<string> DeleteAsync(string url, string data)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, data, HttpMethod.Delete));
        }

        /// <summary>
        /// Выполняет асинхронный DELETE запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> DeleteAsync(string url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, null, HttpMethod.Delete));
        }

        #endregion

        #region Члены IHead

        /// <summary>
        /// Выполняет синхронный HEAD запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Head(Uri url)
        {
            return CreateRequest(url.AbsoluteUri, null, HttpMethod.Head);
        }

        /// <summary>
        /// Выполняет синхронный HEAD запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public string Head(string url)
        {
            return CreateRequest(url, null, HttpMethod.Head);
        }

        #endregion

        #region Члены IHeadAsync

        /// <summary>
        /// Выполняет синхронный HEAD запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> HeadAsync(Uri url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url.AbsoluteUri, null, HttpMethod.Head));
        }

        /// <summary>
        /// Выполняет синхронный HEAD запрос на сервер
        /// </summary>
        /// <param name="url">URL сервера</param>
        public Task<string> HeadAsync(string url)
        {
            return Task<string>.Factory.StartNew(() => CreateRequest(url, null, HttpMethod.Head));
        }

        #endregion
    }
}